// ===========================================================================
// OgreKinectApp.cpp : Source file for OgreKinectApp class
// ---------------------------------------------------------------------------
// Copyright (C) 2011 by Kevin Fisher
// ---------------------------------------------------------------------------
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------
// Author: Kevin Fisher <fisher.kevin.g -at- gmail.com> 
// Date:   Sat Aug 13 2011 17:42 EST
// ================================================================================================================================ 

#include "OgreKinectApp.h"

// ================================================================================================================================

const std::string OgreKinectApp::colorName          = "KinectColorTexture";
const std::string OgreKinectApp::depthName          = "KinectDepthTexture";
const std::string OgreKinectApp::dualName           = "KinectDualTexture";

// ================================================================================================================================ 

/// <summary>
/// Constructor - Initializes the Kinect device.
/// </summary>
/// <remarks>
/// Sets the video format to RGB and turns the Kinect's LED light green.
/// </remarks>
/// <param name="ctx">?</param>
/// <param name="index">The index of the Kinect device. Used for multiple connected devices.</param>
OgreKinectApp::OgreKinectApp(freenect_context *ctx, int index)
	: KinectDevice(ctx, index)
	, videoFullscreen(false)
	, depthFullscreen(false)
	, dualFullscreen(false)
	, angle(0.00)
{
	this->topLeftScreen          = NULL;
	this->topLeftSceneNode       = NULL;

	this->topRightScreen         = NULL;
	this->topLeftSceneNode       = NULL;

	this->bottomScreen           = NULL;
	this->bottomSceneNode        = NULL;

	KinectDevice::setVideoFormat(FREENECT_VIDEO_RGB);
	KinectDevice::setLed(LED_GREEN);
}

// ================================================================================================================================ 

/// <summary>
/// Destructor - Does Nothing
/// </summary>
OgreKinectApp::~OgreKinectApp(void)
{
}

// ================================================================================================================================ 

/// <summary>
/// Sets up the Ogre.
/// </summary>
/// <remarks>
/// Creates the texture and material that will be used to display the feeds.
/// </remarks>
/// <returns>
/// True upon successful setup.
/// </returns>
bool OgreKinectApp::setup(void)
{
	bool ret = BaseApplication::setup();

	this->createTextures();
	this->createMaterial();

	return ret;
}

// ================================================================================================================================ 

/// <summary>
/// Creates the Ogre-Scene. Creates the 2D rectangles for the feed display.
/// </summary>
void OgreKinectApp::createScene(void)
{
	Ogre::Vector3 boxSize = 100000.0 * Ogre::Vector3::UNIT_SCALE;

	// Create the top-left rectangle
	this->topLeftScreen = new Ogre::Rectangle2D(true);
	this->topLeftScreen->setCorners(-1.0, 1.0, 0.0, 0.0);
	this->topLeftScreen->setBoundingBox(Ogre::AxisAlignedBox(-boxSize, boxSize));

	this->topLeftSceneNode = BaseApplication::mSceneMgr->getRootSceneNode()->createChildSceneNode("LeftScreenNode");
	this->topLeftSceneNode->attachObject(this->topLeftScreen);

	// Create the top-right rectangle
	this->topRightScreen = new Ogre::Rectangle2D(true);
	this->topRightScreen->setCorners(0.0, 1.0, 1.0, 0.0);
	this->topRightScreen->setBoundingBox(Ogre::AxisAlignedBox(-boxSize, boxSize));

	this->topRightSceneNode = BaseApplication::mSceneMgr->getRootSceneNode()->createChildSceneNode("RightScreenNode");
	this->topRightSceneNode->attachObject(this->topRightScreen);

	// Create the bottom rectangle
	this->bottomScreen = new Ogre::Rectangle2D(true);
	this->bottomScreen->setCorners(-0.5, 0.0, 0.5, -1.0);
	this->bottomScreen->setBoundingBox(Ogre::AxisAlignedBox(-boxSize, boxSize));

	this->bottomSceneNode = BaseApplication::mSceneMgr->getRootSceneNode()->createChildSceneNode("BottomScreenNode");
	this->bottomSceneNode->attachObject(this->bottomScreen);
}

// ================================================================================================================================ 

/// <summary>
/// Updates the state of the application. Called once during every iteration of the game loop.
/// </summary>
/// <param name="evt">Ogre frame event</param>
/// <returns>
/// True to indicate to the game loop to keep rendering. False to stop the game loop.
/// </returns>
bool OgreKinectApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
	this->update();
	return BaseApplication::frameRenderingQueued(evt);
}

// ================================================================================================================================

/// <summary>
/// Creates the textures that will be used to display the feeds. 
/// </summary>
void OgreKinectApp::createTextures(void)
{
	this->colorTexture = Ogre::TextureManager::getSingleton().createManual(
			OgreKinectApp::colorName,
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
			Ogre::TEX_TYPE_2D,
			KinectDevice::RGB_WIDTH,
			KinectDevice::RGB_HEIGHT,
			0,
			Ogre::PF_R8G8B8,
			Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE
	);

	this->depthTexture = Ogre::TextureManager::getSingleton().createManual(
			OgreKinectApp::depthName,
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
			Ogre::TEX_TYPE_2D,
			KinectDevice::DEPTH_WIDTH,
			KinectDevice::DEPTH_HEIGHT,
			0,
			Ogre::PF_R8G8B8,
			Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE
	);

	this->dualTexture = Ogre::TextureManager::getSingleton().createManual(
			OgreKinectApp::dualName,
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
			Ogre::TEX_TYPE_2D,
			KinectDevice::DEPTH_WIDTH,
			KinectDevice::DEPTH_HEIGHT,
			0,
			Ogre::PF_R8G8B8,
			Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE
	);
}

// ================================================================================================================================

/// <summary>
/// Creates the material for the Kinect Feeds
/// </summary>
void OgreKinectApp::createMaterial(void)
{
	// Create Material for Color Image
	{
		std::string materialName = "KinectColorMaterial";
		Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(
				materialName,
				Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME
		);
		material->getTechnique(0)->getPass(0)->setLightingEnabled(false);
		material->getTechnique(0)->getPass(0)->createTextureUnitState(OgreKinectApp::colorName);

		this->topLeftScreen->setMaterial(materialName);
	}

	// Create Material for Depth Image
	{
		std::string materialName = "KinectDepthMaterial";
		Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(
				materialName,
				Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME
		);
		material->getTechnique(0)->getPass(0)->setLightingEnabled(false);
		material->getTechnique(0)->getPass(0)->createTextureUnitState(OgreKinectApp::depthName);

		this->topRightScreen->setMaterial(materialName);
	}

	// Create Material for Dual Image
	{
		std::string materialName = "KinectDualMaterial";
		Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(
				materialName,
				Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME
		);
		//Ogre::Technique *technique = material->createTechnique();
		material->getTechnique(0)->getPass(0)->setLightingEnabled(false);
		material->getTechnique(0)->getPass(0)->createTextureUnitState(OgreKinectApp::dualName);

		this->bottomScreen->setMaterial(materialName);
	}
}

// ================================================================================================================================ 

/// <summary>
/// Updates the state of the Kinect Device and the Video/Depth feed images.
/// </summary>
void OgreKinectApp::update(void)
{
	KinectDevice::updateState();
	uint8_t rgb[KinectDevice::BUFFER_SIZE]; 
	uint8_t depth[KinectDevice::BUFFER_SIZE];
	uint8_t dual[KinectDevice::BUFFER_SIZE];

	// Get Color / Depth buffer
	this->getDepth(depth);
	this->getRGB(rgb);
	this->mergeBuffers(dual, rgb, depth);

	// Create color pixel box
	Ogre::PixelBox colorBox = Ogre::PixelBox(
			KinectDevice::RGB_WIDTH,
			KinectDevice::RGB_HEIGHT,
			1,
			Ogre::PF_B8G8R8,
			rgb
	);

	// Create depth pixel box
	Ogre::PixelBox depthBox = Ogre::PixelBox(
			KinectDevice::DEPTH_WIDTH,
			KinectDevice::DEPTH_HEIGHT,
			1,
			Ogre::PF_B8G8R8,
			depth
	);

	// Create dual pixel box
	Ogre::PixelBox dualBox = Ogre::PixelBox(
			KinectDevice::DEPTH_WIDTH,
			KinectDevice::DEPTH_HEIGHT,
			1,
			Ogre::PF_B8G8R8,
			dual
	);

	if (!colorTexture.isNull())
	{
		Ogre::HardwarePixelBufferSharedPtr pixelBuffer = colorTexture->getBuffer();
		pixelBuffer->blitFromMemory(colorBox);
		this->new_rgb_frame = false;
	}

	if (!depthTexture.isNull())
	{
		Ogre::HardwarePixelBufferSharedPtr pixelBuffer = depthTexture->getBuffer();
		pixelBuffer->blitFromMemory(depthBox);
		this->new_depth_frame = false;
	}

	if(!dualTexture.isNull())
	{
		Ogre::HardwarePixelBufferSharedPtr pixelBuffer = dualTexture->getBuffer();
		pixelBuffer->blitFromMemory(dualBox);
	}
}

// ================================================================================================================================ 

/// <summary>
/// At a certain depth, the depth feed is displayed, the rest will come from the
/// video feed. 
/// </summary>
/// <param name="buffer">The buffer which the two feeds will be merged into</param>
/// <param name="color">Video Feed</param>
/// <param name="depth">Depth Feed</param>
void OgreKinectApp::mergeBuffers(uint8_t buffer[], uint8_t color[], uint8_t depth[])
{ 
	for(unsigned int i = 0 ; i < 640*480 ; i++)
	{
		bool condition_1 = depth[3*i+0] > 75;
		bool condition_2 = depth[3*i+2] < 75;

		if (!condition_1 || !condition_2)
		{
			buffer[3*i+0] = color[3*i+0];
			buffer[3*i+1] = color[3*i+1];
			buffer[3*i+2] = color[3*i+2];
		}
		else
		{
			buffer[3*i+0] = depth[3*i+0];
			buffer[3*i+1] = depth[3*i+1];
			buffer[3*i+2] = depth[3*i+2];
		}
	}
}

// ================================================================================================================================ 

/// <summary>
/// Handles Key-Pressed events.
/// </summary>
/// <remarks>
/// Constrols:
/// <list type="bullet">
/// <item>[ 1 ] : Toggles video-feed fullscreen.</item>
/// <item>[ 2 ] : Toggles depth-feed fullscreen.</item>
/// <item>[ 3 ] : Toggles dual-feed fullscreen.</item>
/// </list>
/// </remarks>
/// <param name="key_evt">OIS Key Event</param>
/// <returns>
/// Always true.
/// </returns>
bool OgreKinectApp::keyPressed(const OIS::KeyEvent &key_evt)
{
	switch(key_evt.key)
	{
	case OIS::KC_1:
		if (!this->videoFullscreen)
		{
			this->topRightScreen->setVisible(false);
			this->bottomScreen->setVisible(false);

			this->topLeftScreen->setCorners(-1.0, 1.0, 1.0, -1.0);
			this->videoFullscreen = true;
		}
		else
		{
			this->topRightScreen->setVisible(true);
			this->bottomScreen->setVisible(true);

			this->topLeftScreen->setCorners(-1.0, 1.0, 0.0, 0.0);
			this->videoFullscreen = false;
		}

		break;

	case OIS::KC_2:
		if (!this->depthFullscreen)
		{
			this->topLeftScreen->setVisible(false);
			this->bottomScreen->setVisible(false);

			this->topRightScreen->setCorners(-1.0, 1.0, 1.0, -1.0);
			this->depthFullscreen = true;
		}
		else
		{
			this->topLeftScreen->setVisible(true);
			this->bottomScreen->setVisible(true);

			this->topRightScreen->setCorners(0.0, 1.0, 1.0, 0.0);
			this->depthFullscreen = false;
		}

		break;

	case OIS::KC_3:
		if (!this->dualFullscreen)
		{
			this->topLeftScreen->setVisible(false);
			this->topRightScreen->setVisible(false);

			this->bottomScreen->setCorners(-1.0, 1.0, 1.0, -1.0);
			this->dualFullscreen = true;
		}
		else
		{
			this->topLeftScreen->setVisible(true);
			this->topRightScreen->setVisible(true);

			this->bottomScreen->setCorners(-0.5, 0.0, 0.5, -1.0);
			this->dualFullscreen = false;
		}

		break;
	}

	return BaseApplication::keyPressed(key_evt);
}

// ================================================================================================================================ 

// ------------------------------[ END OF CODE ]-------------------------------

/// \file OgreKinectApp.cpp
/// \author Kevin Fisher <fisher.kevin.g -at- gmail.com> 
/// \date \c Created Sat Aug 13 2011 18:10 EST

